(function () {
    /**
     * @see: https://dom.spec.whatwg.org/#event
     */
    class Event {
        type;
        target;
        srcElement; // legacy
        currentTarget;
        static NONE = 0;
        static CAPTURING_PHASE = 1;
        static AT_TARGET = 2;
        static BUBBLING_PHASE = 3;
        eventPhase;
        bubbles;
        cancelable;
        isTrusted; // Legacy Unforgeable
        timeStamp;
        path = [];
        /**
         * flags
         */
        stopPropagationFlag = false;
        stopImmediatePropagationFlag = false;
        canceledFlag = false;
        inPassiveListenerFlag = false;
        composedFlag = false;
        initializedFlag = false;
        dispatchFlag = false;
        constructor(type, eventInitDict) {
            this.initializedFlag = true;
            this.isTrusted = false;
            this.target = null;
            this.type = type;
            this.bubbles = !!eventInitDict?.bubbles;
            this.cancelable = !!eventInitDict?.cancelable;
            this.currentTarget = null;
            this.eventPhase = Event.NONE;
            this.composedFlag = !!eventInitDict?.composed;
            this.timeStamp = 0;
            this.srcElement = null;
        }
        composedPath() {
            /**
             * 1. Let composedPath be an empty list.
             */
            let composedPath = [];
            /**
             * 2. Let path be this’s path.
             * 3. If path is empty, then return composedPath.
             */
            const path = this.path;
            if (path.length === 0) {
                return [];
            }
            if (!this.currentTarget) {
                throw new Error("Error in composedPath: currentTarget is not found.");
            }
            /**
             * 4. Let currentTarget be this’s currentTarget attribute value.
             * 5. Append currentTarget to composedPath..
             * 6. Let currentTargetIndex be 0.
             * 7. Let currentTargetHiddenSubtreeLevel be 0.
             */
            composedPath.push({
                item: this.currentTarget,
                itemInShadowTree: false,
                relatedTarget: null,
                rootOfClosedTree: false,
                slotInClosedTree: false,
                target: null,
                touchTargetList: [],
            });
            let currentTargetIndex = 0;
            let currentTargetHiddenSubtreeLevel = 0;
            /**
             * 7. Let index be path’s size − 1.
             * 8. While index is greater than or equal to 0:
             * 9. If path[index]'s root-of-closed-tree is true, then increase currentTargetHiddenSubtreeLevel by 1.
             *  9-1. If path[index]'s invocation target is currentTarget, then set currentTargetIndex to index and break.
             *  9-2. If path[index]'s slot-in-closed-tree is true, then decrease currentTargetHiddenSubtreeLevel by 1.
             *  9-3. Decrease index by 1.
             */
            for (let i = path.length - 1; i >= 0; i--) {
                const { item, rootOfClosedTree, slotInClosedTree } = path[i];
                if (rootOfClosedTree)
                    currentTargetHiddenSubtreeLevel++;
                if (item === this.currentTarget) {
                    currentTargetIndex = i;
                    break;
                }
                if (slotInClosedTree)
                    currentTargetHiddenSubtreeLevel--;
            }
            /**
             * 10. Let currentHiddenLevel and maxHiddenLevel be currentTargetHiddenSubtreeLevel.
             */
            let currentHiddenLevel = currentTargetHiddenSubtreeLevel;
            let maxHiddenLevel = currentTargetHiddenSubtreeLevel;
            /**
             * 11. Set index to currentTargetIndex − 1.
             * 12. While index is greater than or equal to 0:
             *  12-1. If path[index]'s root-of-closed-tree is true, then increase currentHiddenLevel by 1.
             *  12-2. If currentHiddenLevel is less than or equal to maxHiddenLevel, then prepend path[index]'s invocation target to composedPath.
             *  12-3. If path[index]'s slot-in-closed-tree is true then:
             *   12-3-1. Decrease currentHiddenLevel by 1.
             *   12-3-2. If currentHiddenLevel is less than maxHiddenLevel, then set maxHiddenLevel to currentHiddenLevel.
             * 12-4. Decrease index by 1.
             *
             */
            for (let i = currentTargetIndex - 1; i >= 0; i--) {
                const { item, rootOfClosedTree, slotInClosedTree } = path[i];
                if (rootOfClosedTree)
                    currentHiddenLevel++;
                if (currentHiddenLevel <= maxHiddenLevel) {
                    composedPath.unshift({
                        item,
                        itemInShadowTree: false,
                        relatedTarget: null,
                        rootOfClosedTree: false,
                        slotInClosedTree: false,
                        target: null,
                        touchTargetList: [],
                    });
                }
                if (slotInClosedTree) {
                    currentHiddenLevel--;
                    if (currentHiddenLevel < maxHiddenLevel) {
                        maxHiddenLevel = currentHiddenLevel;
                    }
                }
            }
            /**
             * 13. Set currentHiddenLevel and maxHiddenLevel to currentTargetHiddenSubtreeLevel.
             */
            currentHiddenLevel = currentTargetHiddenSubtreeLevel;
            maxHiddenLevel = currentTargetHiddenSubtreeLevel;
            /**
             * 14. Set index to currentTargetIndex + 1.
             * 15. While index is less than path’s size:
             *  15-1. If path[index]'s slot-in-closed-tree is true, then increase currentHiddenLevel by 1.
             *  15-2. If currentHiddenLevel is less than or equal to maxHiddenLevel, then append path[index]'s invocation target to composedPath.
             *  15-3. If path[index]'s root-of-closed-tree is true, then:
             *   15-3-1. Decrease currentHiddenLevel by 1.
             *   15-3-2. If currentHiddenLevel is less than maxHiddenLevel, then set maxHiddenLevel to currentHiddenLevel.
             * 15-4. Increase index by 1.
             */
            for (let i = currentTargetIndex + 1; i < path.length; i++) {
                const { item, rootOfClosedTree, slotInClosedTree } = path[i];
                if (slotInClosedTree)
                    currentHiddenLevel++;
                if (currentHiddenLevel <= maxHiddenLevel) {
                    composedPath.push({
                        item,
                        itemInShadowTree: false,
                        relatedTarget: null,
                        rootOfClosedTree: false,
                        slotInClosedTree: false,
                        target: null,
                        touchTargetList: [],
                    });
                }
                if (rootOfClosedTree) {
                    currentHiddenLevel--;
                    if (currentHiddenLevel < maxHiddenLevel) {
                        maxHiddenLevel = currentHiddenLevel;
                    }
                }
            }
            /**
             * 16. Return composedPath.
             */
            return composedPath.map((i) => i.item);
        }
        /**
         * The stopPropagation() method steps are to set this’s stop propagation flag.
         */
        stopPropagation() {
            this.stopPropagationFlag = true;
        }
        /**
         * The cancelBubble getter steps are to return true if this’s stop propagation flag is set; otherwise false.
         */
        get cancelBubble() {
            return !!this.stopPropagationFlag;
        }
        /**
         * The cancelBubble setter steps are to set this’s stop propagation flag if the given value is true; otherwise do nothing.
         */
        set cancelBubble(value) {
            if (value)
                this.stopPropagationFlag = true;
        }
        /**
         * The stopImmediatePropagation() method steps are to set this’s stop propagation flag and this’s stop immediate propagation flag.
         */
        stopImmediatePropagation() {
            this.stopImmediatePropagationFlag = true;
            this.stopPropagationFlag = true;
        }
        /**
         * The returnValue getter steps are to return false if this’s canceled flag is set; otherwise true.
         */
        get returnValue() {
            return !!!this.canceledFlag;
        }
        /**
         * The returnValue setter steps are to set the canceled flag with this if the given value is false; otherwise do nothing.
         */
        set returnValue(value) {
            if (!value)
                this.canceledFlag = true;
        }
        /**
         * The preventDefault() method steps are to set the canceled flag with this.
         */
        preventDefault() {
            this.canceledFlag = true;
        }
        get defaultPrevented() {
            return !!this.canceledFlag;
        }
        get composed() {
            return !!this.composedFlag;
        }
        initEvent(type, bubbles, cancelable) { }
        // other setter/getter
        get dispatched() {
            return this.dispatchFlag;
        }
        set dispatched(value) {
            this.dispatchFlag = value;
        }
        get initialized() {
            return this.initializedFlag;
        }
        set initialized(value) {
            this.initializedFlag = value;
        }
    }
    /**
     * @see: https://dom.spec.whatwg.org/#eventtarget
     */
    class EventTarget {
        eventTargetData = {
            listeners: Object.create(null),
        };
        constructor() {
            // The new EventTarget() constructor steps are to do nothing.
        }
        addEventListener(type, callback, options) {
            if (callback === null)
                return;
            const self = this;
            // flatten options
            if (options) {
                options = this.flattenOptions(options);
            }
            else {
                options = {
                    capture: false,
                    once: false,
                    passive: undefined,
                    signal: undefined,
                };
            }
            const { listeners } = self.eventTargetData;
            // init listeners[type]
            if (!listeners[type]) {
                listeners[type] = [];
            }
            const listenerList = listeners[type];
            // check if the same callback is already added then skip
            for (let i = 0; i < listenerList.length; ++i) {
                const listener = listenerList[i];
                const matchWithBooleanOptions = typeof listener.options === "boolean" &&
                    listener.options === options.capture;
                const matchWithObjectOptions = typeof listener.options === "object" &&
                    listener.options.capture === options.capture;
                const matchCallback = listener.callback === callback;
                if ((matchWithBooleanOptions || matchWithObjectOptions) &&
                    matchCallback)
                    return;
            }
            const signal = options.signal;
            // If an AbortSignal is passed for options’s signal, then the event listener will be removed when signal is aborted.
            if (signal) {
                if (signal.aborted) {
                    return;
                }
                else {
                    signal.addEventListener("abort", () => {
                        self.removeEventListener(type, callback, options);
                    });
                }
            }
            listenerList.push({ callback, options });
        }
        removeEventListener(type, callback, options) {
            const self = this;
            const { listeners } = self.eventTargetData;
            const notExistListeners = !listeners[type] || listeners[type].length === 0;
            if (notExistListeners)
                return;
            // flatten options
            if (typeof options === "boolean" || typeof options === "undefined") {
                options = {
                    capture: Boolean(options),
                };
            }
            // remove match listeners
            for (let i = 0; i < listeners[type].length; ++i) {
                const listener = listeners[type][i];
                const matchWithBooleanOptions = typeof listener.options === "boolean" &&
                    listener.options === options.capture;
                const matchWithObjectOptions = typeof listener.options === "object" &&
                    listener.options.capture === options.capture;
                const matchCallback = listener.callback === callback;
                if ((matchWithBooleanOptions || matchWithObjectOptions) &&
                    matchCallback) {
                    listeners[type].splice(i, 1);
                    break;
                }
            }
        }
        dispatchEvent(event) {
            const self = this;
            // If event’s dispatch flag is set, or if its initialized flag is not set, then throw an "InvalidStateError" DOMException.
            if (event.dispatched || !event.initialized) {
                throw new DOMException("Invalid event state.", "InvalidStateError");
            }
            if (event.eventPhase !== Event.NONE) {
                throw new DOMException("Invalid event state.", "InvalidStateError");
            }
            return dispatch(self, event);
        }
        flattenOptions(options) {
            if (typeof options === "boolean") {
                return { capture: options, once: false, passive: false };
            }
            return options;
        }
    }
    // TODO: implement according to https://dom.spec.whatwg.org/#concept-event-dispatch
    function dispatch(eventTarget, event) {
        // Tentative implementation that just calls the callback
        eventTarget.eventTargetData.listeners[event.type].forEach((listener) => {
            listener.callback(event);
        });
        return true;
    }
    Object.assign(globalThis, {
        Event,
        EventTarget,
    });
})();
